using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input.Touch;

namespace Tetris
{
    class Block
    {
        public int X;
        public int Y;
        public Color color;
        bool filled;
        public Block(int newX, int newY, Color newColor, bool isFilled)
        {
            X = newX;
            Y = newY;
            color = newColor;
            filled = isFilled;
        }
        public void fill()
        {
            filled = true;
        }
        public void fill(Color color)
        {
            filled = true;
            this.color = color;
        }
        public void empty()
        {
            filled = false;
        }
        public bool isFilled()
        {
            return filled;
        }
    }
    class L2Segment : Segment
    {
        public L2Segment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 3, newX, newY)
        {
            for (int x = 0; x < 3; x++)
                segmentGrid[0, x].fill(segmentColor);
            segmentGrid[1, 0].fill(segmentColor);
        }
    }
    class L1Segment : Segment
    {
        public L1Segment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 3, newX, newY)
        {
            for (int x = 0; x < 3; x++)
                segmentGrid[0, x].fill(segmentColor);
            segmentGrid[1, 2].fill(segmentColor);
        }
    }
    class OSegment : Segment
    {
        public OSegment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 2, newX, newY)
        {
            for (int x = 0; x < 2; x++)
                segmentGrid[0, x].fill(segmentColor);
            for (int x = 0; x < 2; x++)
                segmentGrid[1, x].fill(segmentColor);
        }
    }
    class Z1Segment : Segment
    {
        public Z1Segment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 3, newX, newY)
        {
            for (int x = 1; x < 3; x++)
                segmentGrid[0, x].fill(segmentColor);
            for (int x = 0; x < 2; x++)
                segmentGrid[1, x].fill(segmentColor);
        }
    }
    class Z2Segment : Segment
    {
        public Z2Segment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 3, newX, newY)
        {
            for (int x = 0; x < 2; x++)
                segmentGrid[0, x].fill(segmentColor);
            for (int x = 1; x < 3; x++)
                segmentGrid[1, x].fill(segmentColor);
        }
    }
    class TSegment : Segment
    {
        public TSegment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 3, newX, newY)
        {
            for (int x = 0; x < 3; x++)
                segmentGrid[1, x].fill(segmentColor);
            segmentGrid[0, 1].fill(segmentColor);
        }
    }
    class ISegment : Segment
    {
        public ISegment(Color segmentColor, int newX, int newY)
            : base(segmentColor, 5, newX, newY)
        {
            for (int x = 0; x < 4; x++)
                segmentGrid[1, x].fill(segmentColor);
        }
    }

    class Segment
    {
        protected Block[,] segmentGrid; //Must be square?
        public Block[,] Grid
        {
            get { return segmentGrid; }
        }
        public int X;
        public int Y;
        private int gridSize;
        public Segment(Color segmentColor, int givenGridSize, int newX, int newY)
        {
            gridSize = givenGridSize;
            segmentGrid = createBaseGrid();
            X = newX;
            Y = newY;
        }
        private Block[,] createBaseGrid()
        {
            Block[,] newGrid = new Block[gridSize, gridSize];
            for (int x = 0; x < gridSize; x++)
                for (int y = 0; y < gridSize; y++)
                    newGrid[x, y] = new Block(x, y, Color.White, false);
            return newGrid;
        }
        public void rotateRight(Block[,] mainGrid)
        {
            Block[,] originalGrid = segmentGrid;
            Block[,] tempGrid = createBaseGrid();
            for (int y = 0; y < gridSize; y++)
                for (int x = 0; x < gridSize; x++)
                    tempGrid[x, y] = segmentGrid[y, (gridSize - 1) - x];
            if (canSegmentRotate(tempGrid, mainGrid))
                segmentGrid = tempGrid;
        }
        public void rotateLeft(Block[,] mainGrid)
        {
            Block[,] originalGrid = segmentGrid;
            Block[,] tempGrid = createBaseGrid();
            for (int y = 0; y < gridSize; y++)
                for (int x = 0; x < gridSize; x++)
                    tempGrid[x, y] = segmentGrid[(gridSize - 1) - y, x];
            if (canSegmentRotate(tempGrid, mainGrid))
                segmentGrid = tempGrid;
        }
        private bool canSegmentRotate(Block[,] tempGrid, Block[,] mainGrid)
        {
            for (int y = 0; y < gridSize; y++)
                for (int x = 0; x < gridSize; x++)
                    if (tempGrid[x, y].isFilled())
                        if ((X + x < 0 || (X + x + 1) > mainGrid.GetLength(0)) || (Y + y < 0 || (Y + y + 1) > mainGrid.GetLength(1)))
                            return false;
                        else if (mainGrid[x + X, y + Y].isFilled())
                            return false;
            return true;
        }
        public void Draw(SpriteBatch spriteBatch, Texture2D solidBlockTexture, Vector2 gridTopLeft)
        {
            for (int x = 0; x < gridSize; x++)
                for (int y = 0; y < gridSize; y++)
                    if (segmentGrid[x, y].isFilled())
                        spriteBatch.Draw(solidBlockTexture, gridTopLeft + new Vector2((X + x) * solidBlockTexture.Width, (Y + y) * solidBlockTexture.Height), segmentGrid[x, y].color);
        }
    }
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;


        // Keyboard states used to determine key presses
        KeyboardState currentKeyboardState;
        KeyboardState previousKeyboardState;

        // A fall speed for the block
        float blockFallTime;
        const float STARTBLOCKFALLTIME = 450f;//650
        float lastBlockFallTime = 0f;
        // Images used to display the blocks
        Texture2D solidBlockTexture;
        Texture2D emptyBlockTexture;

        // A random number generator
        Random random;

        // The music played during gameplay
        //Song gameplayMusic;

        private Vector2 gridTopLeft = new Vector2(150, 120);
        private Vector2 nextSegmentPosition = new Vector2(500, 200);
        //Number that holds the player score
        int score;

        // The font used to display UI elements
        SpriteFont font;

        //Vector2 that equals the center of the screen
        private Vector2 centerOfScreen;
        private const int GRIDHEIGHT = 20;
        private const int GRIDWIDTH = 10;

        Block[,] mainGrid;

        Segment activeSegment;
        Segment nextSegment;
        Random R = new Random();
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 600;
            graphics.PreferredBackBufferHeight = 800;
            //graphics.IsFullScreen = true;
            //graphics.ApplyChanges();
            Content.RootDirectory = "Content";
            centerOfScreen = new Vector2(graphics.PreferredBackBufferWidth / 2, graphics.PreferredBackBufferHeight / 2);
        }

        protected override void Initialize()
        {
            random = new Random();
            score = 0;
            blockFallTime = STARTBLOCKFALLTIME;
            resetGrid();
            nextSegment = getNewSegment();
            setUpNewActiveSegment();
            base.Initialize();
        }

        private void setUpNewActiveSegment()
        {
            activeSegment = nextSegment;
            nextSegment = getNewSegment();
        }

        private Segment getNewSegment()
        {
            int nextBlock = R.Next(0, 6);
            Segment newSegment = new Segment(Color.White, 0, 0, 0);
            switch (nextBlock)
            {
                case 0:
                    newSegment = new L1Segment(Color.Blue, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 1:
                    newSegment = new L2Segment(Color.Yellow, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 2:
                    newSegment = new Z1Segment(Color.White, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 3:
                    newSegment = new Z2Segment(Color.Violet, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 4:
                    newSegment = new ISegment(Color.RoyalBlue, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 5:
                    newSegment = new OSegment(Color.Red, GRIDWIDTH / 2 - 1, 0);
                    break;
                case 6:
                    newSegment = new TSegment(Color.White, GRIDWIDTH / 2 - 1, 0);
                    break;
            }
            return newSegment;
        }

        private void resetGrid()
        {
            mainGrid = new Block[GRIDWIDTH, GRIDHEIGHT];
            for (int x = 0; x < GRIDWIDTH; x++)
                for (int y = 0; y < GRIDHEIGHT; y++)
                    mainGrid[x, y] = new Block(x, y, Color.White, false);
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            emptyBlockTexture = Content.Load<Texture2D>("blockEmpty");
            solidBlockTexture = Content.Load<Texture2D>("blockSolid");



            // Load the score font
            font = Content.Load<SpriteFont>("gameFont");
        }


        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (currentKeyboardState.IsKeyDown(Keys.Escape))
                this.Exit();
            updateActiveSegment(gameTime);
            base.Update(gameTime);
        }

        private void updateActiveSegment(GameTime gameTime)
        {
            currentKeyboardState = Keyboard.GetState();

            
            lastBlockFallTime += gameTime.ElapsedGameTime.Milliseconds;
            if (lastBlockFallTime > blockFallTime || (currentKeyboardState.IsKeyDown(Keys.S) && previousKeyboardState.IsKeyDown(Keys.S) && lastBlockFallTime > blockFallTime / 5))
            {
                if (doesSegmentHaveAnythingBelowIt())
                    mergeSegmentWithGrid();
                else
                    activeSegment.Y = Math.Min(GRIDHEIGHT - 1, activeSegment.Y + 1);
                lastBlockFallTime = 0;
            }
            if (currentKeyboardState.IsKeyDown(Keys.A) && previousKeyboardState.IsKeyUp(Keys.A) && !doesSegmentHaveAnythingToTheLeft())//Left
                activeSegment.X = Math.Max(-activeSegment.Grid.GetLength(0), activeSegment.X - 1);
            if (currentKeyboardState.IsKeyDown(Keys.D) && previousKeyboardState.IsKeyUp(Keys.D) && !doesSegmentHaveAnythingToTheRight())//Right
                activeSegment.X = Math.Min(GRIDWIDTH - 1, activeSegment.X + 1);
            //if (currentKeyboardState.IsKeyDown(Keys.W) && previousKeyboardState.IsKeyUp(Keys.W) && !checkIfGridIsFilledAtCoord(activeBlock.X, Math.Min(GRIDHEIGHT - 1, activeBlock.Y + 1)))//Down
              //  activeBlock.Y = Math.Min(GRIDHEIGHT - 1, activeBlock.Y + 1);
            if (currentKeyboardState.IsKeyDown(Keys.E) && previousKeyboardState.IsKeyUp(Keys.E))// && !doesSegmentHaveAnythingToTheRight())// Rotate Right
                activeSegment.rotateRight(mainGrid);
            if (currentKeyboardState.IsKeyDown(Keys.Q) && previousKeyboardState.IsKeyUp(Keys.Q))// && !doesSegmentHaveAnythingToTheRight())// Rotate Right
                activeSegment.rotateLeft(mainGrid);
            previousKeyboardState = currentKeyboardState;
        }

        private void mergeSegmentWithGrid()
        {
            Block[,] tempGrid = activeSegment.Grid;
            for (int x = 0; x < tempGrid.GetLength(0); x++)
                for (int y = 0; y < tempGrid.GetLength(1); y++)
                    if (tempGrid[x, y].isFilled())
                    {
                        mainGrid[x + activeSegment.X, y + activeSegment.Y] = tempGrid[x, y];
                        if (checkForFullLine(activeSegment.Y + y))
                            EmptyLine(activeSegment.Y + y);
                    }
            setUpNewActiveSegment();
        }
        
        private bool doesSegmentHaveAnythingToTheLeft()
        {
            Block[,] tempGrid = activeSegment.Grid;
            for (int x = 0; x < tempGrid.GetLength(0); x++)
                for (int y = 0; y < tempGrid.GetLength(1); y++)
                    if (tempGrid[x, y].isFilled())
                        if (x + activeSegment.X < 1 || checkIfGridIsFilledAtCoord(Math.Max(0, (activeSegment.X + x) - 1), y + activeSegment.Y))
                            return true;
            return false;
        }
        private bool doesSegmentHaveAnythingToTheRight()
        {
            Block[,] tempGrid = activeSegment.Grid;
            for (int x = 0; x < tempGrid.GetLength(0); x++)
                for (int y = 0; y < tempGrid.GetLength(1); y++)
                    if (tempGrid[x, y].isFilled())
                        if ((x + activeSegment.X + 1) > (GRIDWIDTH - 1) || checkIfGridIsFilledAtCoord(Math.Min(GRIDWIDTH -1, (activeSegment.X + x) + 1), y + activeSegment.Y))
                            return true;
            return false;
        }
        private bool doesSegmentHaveAnythingBelowIt()
        {
            Block[,] tempGrid = activeSegment.Grid;
            for (int x = 0; x < tempGrid.GetLength(0); x++)
                for (int y = 0; y < tempGrid.GetLength(1); y++)
                    if (tempGrid[x, y].isFilled())
                        if ((y + 2) + activeSegment.Y > GRIDHEIGHT || checkIfGridIsFilledAtCoord(x + activeSegment.X, Math.Max(0, activeSegment.Y + y + 1)))
                            return true;
            return false;
        }

        private void EmptyLine(int y)
        {
            for (int x = 0; x < GRIDWIDTH; x++)
                mainGrid[x, y].empty();
            for (int H = y; H > 1; H--)
                for (int x = 0; x < GRIDWIDTH; x++)
                    mainGrid[x, H] = mainGrid[x, H - 1];
            for (int x = 0; x < GRIDWIDTH; x++)
                mainGrid[x, 0].empty();
            score += 10;
        }
        private bool checkForFullLine(int y)
        {
            for (int x = 0; x < GRIDWIDTH; x++)
                if (!mainGrid[x, y].isFilled())
                {
                    return false;
                }
            return true;
        }

        private bool checkIfGridIsFilledAtCoord(int x, int y)
        {
            return mainGrid[x, y].isFilled();
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin();

            spriteBatch.DrawString(font, "Score: " + score, Vector2.Zero, Color.White);
            spriteBatch.DrawString(font, "Next Segment: ", nextSegmentPosition - new Vector2(35, 60), Color.White);

            for (int x = 0; x < GRIDWIDTH; x++)
                for (int y = 0; y < GRIDHEIGHT; y++)
                    spriteBatch.Draw(emptyBlockTexture, gridTopLeft + new Vector2(x * emptyBlockTexture.Width, y * emptyBlockTexture.Height), Color.White);
            for (int x = 0; x < GRIDWIDTH; x++)
                for (int y = 0; y < GRIDHEIGHT; y++)
                    if (mainGrid[x, y].isFilled())
                        spriteBatch.Draw(solidBlockTexture, gridTopLeft + new Vector2(x * solidBlockTexture.Width, y * solidBlockTexture.Height), mainGrid[x, y].color);
            activeSegment.Draw(spriteBatch, solidBlockTexture, gridTopLeft);

            for (int x = 0; x < nextSegment.Grid.GetLength(0); x++)
                for (int y = 0; y < nextSegment.Grid.GetLength(0); y++)
                    if (nextSegment.Grid[x, y].isFilled())
                        spriteBatch.Draw(solidBlockTexture, nextSegmentPosition + new Vector2(x * solidBlockTexture.Width, y * solidBlockTexture.Height), nextSegment.Grid[x, y].color);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
/*
 * KNOWN BUGS
 * (fixed? )completing a line with a horizontal piece removes only the most left part and leaves the rest
 * 
 * 
 * Block[,] grid;
 * Segment activeSegment;
 * 
 * Block class 
 * Color color
 * bool filled
 * Segment
 * T Segment ===
 *            =
 * Z1 Segment ==
 *             ==
 * Z2 Segment  ==
 *            ==
 * I Segment ====
 * L1 Segment ===
 *              =
 * L2 Segment ===
 *            =  
 * O Segment ==
 *           ==
 * TODO 
 * 
 * Make W(UP) Key drop instantly
 * DONE show next segment to appear
 * put in alternate keys (arrows and something for rotating)
 * DONE rotate
 * DONE setup all types of blocks 
 * Levels (more speed/ more points)
 * Add Losing functionality
 * Highscore .txt
 * DONE Add rotation collision with blocks and sides and bottom
 */

